文章目录
  1. 1. 第23条:请不要再新代码中使用原生态类型
  2. 2. 第24条:消除非首检警告
  3. 3. 第25条:列表优先于数组
  4. 4. 第26条:优先考虑泛型
  5. 5. 第27条:优先考虑泛型方法
  6. 6. 第28条:利用有限制通配符来提升API的灵活性
  7. 7. 第29条:优先考虑类型安全的异构容器

泛型,可以告诉编译器每个集合中接受哪些对象类型,编译器会自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。(貌似Java里面的泛型都是伪泛型吧-_-|)

第23条:请不要再新代码中使用原生态类型

什么是原生态类型?

类似List<E>,Collection<E>泛型的定义都有一个原生态类型,那就是List,Collection,用他们定义类型也不会出错.

为什么不要使用原生态类型

使用原生态类型的集合类可以插入各种不同的类型,他们在编译时不会进行类型检查,但是运行时遇到错误就会抛出,这样是很不安全的。

我在不确定或者不在乎集合类型的情况下,是不是用原生态类型最好?

还是不要用,可以使用无限制的通配符来替代,比如

1
2
3
4
5
6
7
8
9
10
11
static int numElementsInCommon(Set<?> s1,Set<?> s2)
{

int count=0;
for(Object o:s1)
{
if(s2.contains(o))
count++;
}

return count;
}

它是类型安全的,但是无法猜测放入了哪些对象,此时可以使用泛型或者有限制的通配符类型(下文会描述)。

第24条:消除非首检警告

非受检警告很重要,每个警告都表示可能在运行时抛出ClassCastException异常,我们应该在编码时尽量消除编辑器给出的每一条受检警告。如果无法消除非首检警告,同时可证明引起警告的代码是类型安全的,就可以在尽量小得范围中,用@SuppressWarnings(“uncheck”)注解来禁止该警告,并且要把禁止该警告的原因记录下来。

第25条:列表优先于数组

数组与泛型相比,有两个重要的不同点:

  1. 数组是协变的,泛型则是不可变的
    如果SubSuper的子类,那么数组类型Sub[]就是Super[]的子类,但是List<Sub>List<Super>并没有什么卵关系。

    1
    2
    3
    4
    5
    Object[] objectArray=new Long[10];
    objectArray[0]="Hello";//运行时会再这里抛ArrayStoreException异常

    List<Object> list=new List<Long>();//在编译时就在这里出错了
    list.add("Hello");

    从上面看出来,它们都不能将String放入Long容器中,但是明显泛型的报错更加合理。

2.数组是具体化的,因此在运行时才会知道并检查它们元素类型的约束,而泛型是通过擦除来实现的,因此泛型时只在编译时强化它们的类信息,并在运行时丢弃。

泛型是数组不能很好的混合使用,使用泛型数组定义式非法的。

第26条:优先考虑泛型

使用泛型简单,但是自己编写泛型还是有一点麻烦的,下面是第6条中的两种泛型版本。
1.使用泛型E[]来存储数据,但是用Object[]的数组转换来处理泛型数组的创建错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 简单的栈结构的实现 泛型版本-泛型数组转换
* @author yyl form Effective Java
*
*/

public class Stack<E> {
private E[] elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPCAITY=16;

/**
* 这个elements元素只会使用E类型来添加
* 所以他是足够安全的
*/

@SuppressWarnings("unchecked")
public Stack()
{

elements=(E[])new Object[DEFAULT_INITIAL_CAPCAITY];
}

public void push(E e)
{

ensureCapacity();//确保容量足够
elements[size++]=e;
}


public E pop()
{

if(size==0)
throw new EmptyStackException();
return elements[--size];//
}

private void ensureCapacity()
{

if(elements.length==size)
elements=Arrays.copyOf(elements, 2*size+1);
}
}

  1. 使用Object[]来存储,但是返回类型使用Object向泛型类型E去转换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 简单的栈结构的实现 泛型版本-泛型转换
    * @author yyl form Effective Java
    *
    */

    public class Stack<E> {
    private Object[] elements;
    private int size=0;
    private static final int DEFAULT_INITIAL_CAPCAITY=16;

    public Stack()
    {

    elements=new Object[DEFAULT_INITIAL_CAPCAITY];
    }

    public void push(E e)
    {

    ensureCapacity();//确保容量足够
    elements[size++]=e;
    }

    /**
    * 这里都是需要返回E
    * 所以这里的转换是安全的
    */

    @SuppressWarnings("unchecked")
    public E pop()
    {

    if(size==0)
    throw new EmptyStackException();
    return (E)elements[--size];//
    }

    private void ensureCapacity()
    {

    if(elements.length==size)
    elements=Arrays.copyOf(elements, 2*size+1);
    }
    }

上述优先选择的是第二种方案,第一种禁止数组类型的非受检转换比禁止标量类型更加危险,并且它需要多次转换E[],而第二是是多次转换E

第27条:优先考虑泛型方法

关于泛型方法和非泛型方法可以见下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 原生类型的书写,类型不安全,并且还会产生警告
*/

public static Set union2(Set s1,Set s2)
{

Set result=new HashSet(s1);
result.addAll(s2);
return result;
}

/**
* 泛型方法,类型安全
*/

public static <E> Set<E> union(Set<E> s1,Set<E> s2)
{

Set<E> result=new HashSet<E>(s1);
result.addAll(s2);
return result;
}

很明显,泛型方法更加安全,并且简单易用。

使用泛型方法就像类型异性,还应该将现有的方法泛型化,使新用户使用起来更加轻松,且不会平均破坏现有的客户端。

第28条:利用有限制通配符来提升API的灵活性

针对第26条中泛型Stack的实现方法,如果现在需要添加pushAllpopAll这两个方法,为了让他们用起来更加顺手,那么应该这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 所有E的子类的集合都可以添加进来
* @param iter
*/

public void pushAll(Iterable<? extends E> iter)
{

for(E e:iter)
this.push(e);
}

/**
* 可以将elements里面的元素全部弹出到E得超类中
* @param dst
*/

public void popAll(Collection<? super E> dst)
{

while(!isEmpty())
dst.add(pop());
}

使用通配符能让API变得更加灵活,在编写广泛使用的类库时,一定适当地利用通配符类型。

第29条:优先考虑类型安全的异构容器

泛型往往会被用户参数化了的容器,每个容器有固定数目的类型参数。但是你可能需要更加灵活地容器,比如数据库的表可以有任意多得列,在Java中,解决这个问题的做法就是将建参数化而不是容器参数化。可以使用类类型Class来作为键值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static class Favorites{
private Map<Class<?>,Object> favorites=new HashMap<Class<?>,Object>();

/**
* 存值
* @param type 这个是键,每个键的类型都是不同的
* @param instance 这个就是值
*/

public <T> void putFavorite(Class<T> type,T instance)
{

if(type==null)
throw new NullPointerException("Type is null");

/**
* 这里加上cast可以防止用户恶意改传类型
*/

favorites.put(type, type.cast(instance));
}

public <T> T getFavorites(Class<T> type)
{

/**
* 这里的cast转换是安全的,因为放入的都是对应T的类型的值
*/

return type.cast(favorites.get(type));
}

}

这个Favorites类型里面的键都不同类型的,这里将Favorites称为异构容器。

它又两个局限性:

  1. 用户可以轻松地破坏Favorites的实例安全(可以用put方法中cast来避免)
  2. 它只能用于可具体化的类ing中(比如List这个类型你就不能传过去)
文章目录
  1. 1. 第23条:请不要再新代码中使用原生态类型
  2. 2. 第24条:消除非首检警告
  3. 3. 第25条:列表优先于数组
  4. 4. 第26条:优先考虑泛型
  5. 5. 第27条:优先考虑泛型方法
  6. 6. 第28条:利用有限制通配符来提升API的灵活性
  7. 7. 第29条:优先考虑类型安全的异构容器